/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package org.atomojo.tools;

import java.io.*;
import java.net.URI;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
import java.util.TreeSet;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.restlet.Client;
import org.restlet.Context;
import org.restlet.data.Protocol;
import org.restlet.data.Reference;
import org.restlet.representation.Representation;
import org.w3c.dom.Document;
import org.w3c.dom.Element;

/**
 *
 * @author alex
 */
public class Backup extends DocumentWorker {
   static final String ATOM_NS = "http://www.w3.org/2005/Atom";
   static final String APP_NS = "http://www.w3.org/2007/app";
   static Logger LOG = Logger.getLogger(Backup.class.getName()); 
   
   public static void usage() {
      System.err.println("Usage: "+Backup.class.getName()+" ( -token server=token ) ( -auth server=(username{:password} | - )) service target-dir");
   }
   
   public static String [] getServerArgument(String value)
   {
      int eq = value.indexOf('=');
      if (eq<0) {
         return null;
      }
      String [] values = new String[2];
      values[0] = value.substring(0,eq);
      values[1] = value.substring(eq+1);
      return values;
   }
   
   public static void main(String[] args) {
      Context context = new Context();
      context.setLogger(LOG);
      
      List<Protocol> clientProtocols = new ArrayList<Protocol>();
      clientProtocols.add(Protocol.HTTP);
      clientProtocols.add(Protocol.HTTPS);
      Client client = new Client(clientProtocols);
      client.setContext(context);
      
      context.setClientDispatcher(client);
      
      Console console = System.console();
      UserAgent browser = new UserAgent(context);
      int argidx = 0;
      while (argidx<args.length && args[argidx].charAt(0)=='-') {
         if (args[argidx].equals("-token")) {
            argidx++;
            String [] spec = getServerArgument(args[argidx]);
            browser.setAccessToken(spec[0], spec[1]);
         } else if (args[argidx].equals("-auth")) {
            argidx++;
            String [] spec = getServerArgument(args[argidx]);
            if (spec[1].equals("-")) {
               String username = console.readLine("Username:");
               char [] password = console.readPassword("Password:");
               browser.setIdentity(spec[0], username, new String(password));
            } else {
               String [] auth = args[argidx].split(":");
               browser.setIdentity(spec[0], auth[0], auth[1]);
            }
         }
         argidx++;
      }
      
      if ((args.length-argidx)<2) {
         usage();
         System.exit(1);
      }
      
      Reference serviceRef = new Reference(args[argidx]);
      File targetDir = new File(args[argidx+1]);
      if (!targetDir.exists()) {
         if (!targetDir.mkdirs()) {
            LOG.severe("Cannot create directory/path: "+targetDir.getAbsolutePath());
            System.exit(1);
         }
      }
      
      if (!targetDir.isDirectory()) {
         LOG.severe("Not a directory: "+targetDir.getAbsolutePath());
         System.exit(1);
      }
      
      Backup backup = new Backup(browser);
      if (!backup.backupService(serviceRef, targetDir)) {
         LOG.severe("Backup failed.");
         System.exit(1);
      }
      LOG.info("Backup complete.");
      System.exit(0);
   }
   
   public Backup(UserAgent agent) 
   {
      super(LOG,agent);
   }
   
   public boolean backupService(Reference serviceRef,File targetDir)
   {
      LOG.info("Getting service: "+serviceRef);
      URI collectionBase = URI.create(serviceRef.toString());
      Document serviceDoc = getDocument(serviceRef);
      if (serviceDoc==null) {
         return false;
      }
      Set<URI> downloadedCollections = new TreeSet<URI>();
      List<Element> workspaces = getChildrenByName(serviceDoc.getDocumentElement(),APP_NS,"workspace");
      for (Element workspace : workspaces) {
         List<Element> collections = getChildrenByName(workspace,APP_NS,"collection");
         for (Element collection : collections) {
            String href = collection.getAttribute("href");
            URI base = URI.create(collection.getBaseURI());
            URI location = base.resolve(href);
            if (downloadedCollections.contains(location)) {
               // already downloaded
               continue;
            }
            URI relRef = collectionBase.relativize(location);
            LOG.info("Processing: "+location+" -> "+relRef);
            collection.setAttribute("href", relRef+"__index__.atom");
            Document feedDoc = getDocument(new Reference(location.toString()));
            if (feedDoc==null) {
               return false;
            }
            File feedDir = new File(targetDir,relRef.toString());
            if (!feedDir.exists()) {
               if (!feedDir.mkdirs()) {
                  getLogger().severe("Cannot make feed directory: "+feedDir.getAbsolutePath());
                  return false;
               }
            }
            File feedFile = new File(feedDir,"__index__.atom");
            try {
               Writer output = new OutputStreamWriter(new FileOutputStream(feedFile),"UTF-8");
               writeDocument(feedDoc,output);
               output.close();
            } catch (IOException ex) {
               getLogger().log(Level.SEVERE,"Cannot write feed document "+feedFile.getAbsolutePath()+" due to I/O exception.",ex);
               return false;
            }
            if (!backupMedia(feedDir,feedDoc)) {
               return false;
            }
            downloadedCollections.add(location);
         }
      }
      File serviceFile = new File(targetDir,"service.xml");
      try {
         Writer output = new OutputStreamWriter(new FileOutputStream(serviceFile),"UTF-8");
         writeDocument(serviceDoc,output);
         output.close();
      } catch (IOException ex) {
         getLogger().log(Level.SEVERE,"Cannot write service document due to I/O exception.",ex);
         return false;
      }
      return true;
   }
   
   public boolean backupMedia(File targetDir,Document feedDoc)
   {
      List<Element> entries = getChildrenByName(feedDoc.getDocumentElement(), ATOM_NS, "entry");
      byte [] buffer = new byte[4096];
      for (Element entry : entries) {
         Element content = findFirst(entry,ATOM_NS,"content");
         if (content!=null && content.hasAttribute("src")) {
            String src = content.getAttribute("src");
            if (src!=null) {
               URI base = URI.create(content.getBaseURI());
               Reference mediaRef = new Reference(base.resolve(src).toString());
               getLogger().info("Downloading: "+mediaRef);
               Representation entity = getMedia(mediaRef);
               if (entity==null) {
                  return false;
               }
               InputStream in = null;
               OutputStream out = null;
               try {
                  in = entity.getStream();
                  out = new FileOutputStream(new File(targetDir,src));
                  int len;
                  while ((len=in.read(buffer))>0) {
                     out.write(buffer,0,len);
                  }
               } catch (IOException ex) {
                  getLogger().log(Level.SEVERE,"Cannot download media due to I/O error.",ex);
                  return false;
               } finally {
                  if (in!=null) {
                     try {
                        in.close();
                     } catch (IOException ioex) {}
                  }
                  if (out!=null) {
                     try {
                        out.close();
                     } catch (IOException ioex) {}
                  }
                  entity.release();
               }
            }
         }
      }
      return true;
   }
   
   
}
